﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using FinPlusCommon;
using log = Logger.Logger;

namespace FinPlusComponents
{
    public class FinPlusQuoteEngine : FinPlusComponent
    {
        public override string[] TestStates { get { return new string[] { "TestString", "IsValid" }; } }
        public override IFunctions Adaptor { get { return _instrumentConn.Adaptor; } }
        public override string TestString { get { return Adaptor.ToString(); } }
  
        private enum Params { Instrument, InstrumentType, Counterparty, CurveName, DefaultCollateralCurve, FunctionName, QuoteInstrument, QuoteSource, ParseTradeAction, ReceiveAction, PublishAction, ResponseAction,Tag }
        private readonly IFinPlusComp _instrumentConn, _spreadConn, _quoteInterestConn, _counterpartyConn, _counterpartyTierConn, _quoteAdaptorConn, _tradeAdaptorConn;
        private readonly FinPlusConnectDB _saveClosesConn;
        private readonly FinPlusQuoteInstruments _quoteInstruments;
        private readonly ConcurrentDictionary<string, IFunction> _quoteAdaptors;
        private readonly ConcurrentDictionary<string, FinPlusQuoteWrapper> _quotes, _publishQuotes;

        //construct
        public FinPlusQuoteEngine(IFinPlusComp instruments, IFinPlusComp spreads, IFinPlusComp quoteInterests, IFinPlusComp counterpartys,
            IFinPlusComp counterpartyTiers, IFinPlusComp quoteAdaptors, IFinPlusComp tradeAdaptors, IFinPlusComp saveCloses)
        {
            _instrumentConn = instruments;
            _spreadConn = spreads;
            _quoteInterestConn = quoteInterests;
            _counterpartyConn = counterpartys;
            _counterpartyTierConn = counterpartyTiers;
            _quoteAdaptorConn = quoteAdaptors;
            _tradeAdaptorConn = tradeAdaptors;
            _saveClosesConn = (FinPlusConnectDB)saveCloses;
            _quoteInstruments = new FinPlusQuoteInstruments();
            _quotes = new ConcurrentDictionary<string, FinPlusQuoteWrapper>();
            _publishQuotes = new ConcurrentDictionary<string, FinPlusQuoteWrapper>();
            _quoteAdaptors = new ConcurrentDictionary<string, IFunction>();
            IsValid = true;

            Load();
        }

        //common control interface
        public void Load()
        {
            try
            {
                //rename instruments
                var counterpartyCollateralCurves = _counterpartyConn.Adaptor.ToDictionary(Params.Counterparty.ToString(), Params.DefaultCollateralCurve.ToString());
                foreach (var instrument in _instrumentConn.Adaptor.Values)
                    instrument.Get(Params.FunctionName.ToString()).Set(instrument.Name = Params.QuoteInstrument.ToString());

                //add ecns
                foreach (var func in _quoteAdaptorConn.Adaptor.Values)
                {
                    _quoteAdaptors[func[Params.QuoteSource.ToString()].ToString()] = func;
                    func.Get(Params.ReceiveAction.ToString()).PropertyChanged += Quote_Receive;
                }

                //build instruments
                foreach (var instrument in _instrumentConn.Adaptor.Values)
                    _quoteInstruments[instrument[Params.Instrument.ToString()].ToString()] = new FinPlusQuoteInstrument(instrument, counterpartyCollateralCurves, _quoteAdaptors.Values.Select(f => f[Params.QuoteSource.ToString()].ToString()).ToList<string>(), Quote_Publish);

                //add spreads
                foreach (var quoteInstrument in _quoteInstruments.Values)
                    quoteInstrument.AddSpreads(_spreadConn.Adaptor.ToArray(Params.Instrument.ToString(), quoteInstrument.Instrument), _quoteInstruments);

                //add quote interests
                foreach (var quoteInstrument in _quoteInstruments.Values)
                    quoteInstrument.AddQuoteInterests(_quoteInterestConn.Adaptor.ToArray(Params.Instrument.ToString(), quoteInstrument.Instrument));


                //get trades back after quote parse for testing
                foreach (var tradeAdaptor in _tradeAdaptorConn.Adaptor.Values)
                    tradeAdaptor.Get(Params.ResponseAction.ToString()).PropertyChanged += Trade_Done;
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void CmdExecute(IFunction command)
        {
            try
            {
                CmdCapture(command);

                //SaveAudit(func.Name, new IFinPlusFunction[] { func });

                switch ((Cmds)Enum.Parse(typeof(Cmds), command.Name, true))
                {
                    case Cmds.SaveCloses: SaveCloses(command); break;
                    case Cmds.Quote: Quote(command); break;
                    case Cmds.QuoteCancel: QuoteCancel(command); break;
                    case Cmds.QuoteExecute: QuoteExecute(command); break;
                    case Cmds.QuoteMissed: QuoteMissed(command); break;
                    case Cmds.QuoteAccept: QuoteAccept(command); break;
                    case Cmds.QuoteDecline: QuoteDecline(command); break;
                    case Cmds.QuotePublish: QuotePublish(command); break;
                    case Cmds.QuoteCancelPublish: QuoteCancelPublish(command); break;
                    case Cmds.QuoteAcceptPublish: QuoteAcceptPublish(command); break;
                    case Cmds.QuoteDeclinePublish: QuoteDeclinePublish(command); break;
                    case Cmds.QuoteExecutePublish: QuoteExecutePublish(command); break;
                    case Cmds.BookedTrade: BookedTrade(command); break;
                    default: throw new Exception(string.Format("event not recognised {0}", command.Name));
                }
            }
            catch (CmdExecuteException e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Dispose()
        {
            try
            {
                if (_quoteInstruments != null)
                    _quoteInstruments.Dispose();
                if (_instrumentConn != null)
                    _instrumentConn.Dispose();
                if (_saveClosesConn != null)
                    _saveClosesConn.Dispose();
                if (_spreadConn != null)
                    _spreadConn.Dispose();
                if (_quoteInterestConn != null)
                    _quoteInterestConn.Dispose();
                if (_counterpartyConn != null)
                    _counterpartyConn.Dispose();
                if (_counterpartyTierConn != null)
                    _counterpartyTierConn.Dispose();
                if (_quoteAdaptorConn != null)
                    _quoteAdaptorConn.Dispose();
                if (_tradeAdaptorConn != null)
                    _tradeAdaptorConn.Dispose();
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Warning, e.Message, e);
            }
        }

        //private
        private void SaveCloses(IFunction func)
        {
            if(_saveClosesConn != null)
                foreach (var quoteInstrument in _quoteInstruments.Values)
                    foreach (var buySell in quoteInstrument.Trader.BuySells.Values)
                        _saveClosesConn.Save(buySell.MarketLevel);
        }

        private void Quote(IFunction func)
        {
            try
            {
                var quote = new FinPlusQuoteWrapper(func);
                FinPlusQuoteInstrument instrument;
                if (_quoteInstruments.TryGetValue(quote.Instrument, out instrument) && _quotes.TryAdd(quote.QuoteRef, quote))
                    instrument.Quote(quote);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteCancel(IFunction func)
        {
            try
            {
                var cancel = new FinPlusQuoteWrapper(func);
                FinPlusQuoteWrapper quote; 
                FinPlusQuoteInstrument instrument;
                if (_quotes.TryRemove(cancel.QuoteRef, out quote) && _quoteInstruments.TryGetValue(quote.Instrument, out instrument))
                    instrument.QuoteCancel(quote);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteExecute(IFunction func)
        {
            try
            {
                var quoteExectue = new FinPlusQuoteWrapper(func);
                IFunction quoteAdaptor;
                if (_quotes.ContainsKey(quoteExectue.QuoteRef) && _quoteAdaptors.TryGetValue(quoteExectue.SourceName, out quoteAdaptor))
                {
                    quoteAdaptor.Get(Params.PublishAction.ToString()).Set(quoteExectue.Quote);
                }
                else
                {
                    func.Get(Params.FunctionName.ToString()).Set(func.Name = Cmds.QuoteMissed.ToString());
                    Quote_Publish(func);
                }
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteMissed(IFunction func)
        {
            try
            {
                var quoteMissed = new FinPlusQuoteWrapper(func);
                FinPlusQuoteInstrument instrument;
                if (_quoteInstruments.TryGetValue(quoteMissed.Instrument, out instrument))
                    instrument.QuoteMissed(quoteMissed);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteDecline(IFunction func)
        {
            try
            {
                var quoteDecline = new FinPlusQuoteWrapper(func);
                FinPlusQuoteInstrument instrument;
                if (_quoteInstruments.TryGetValue(quoteDecline.Instrument, out instrument))
                    instrument.QuoteDecline(quoteDecline);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteAccept(IFunction func)
        {
            try
            {
                var quoteAccept = new FinPlusQuoteWrapper(func);
                FinPlusQuoteInstrument instrument;
                if (_quoteInstruments.TryGetValue(quoteAccept.Instrument, out instrument))
                {
                    foreach (var tradeAdaptor in _tradeAdaptorConn.Adaptor.Values.Where(f => f[Params.InstrumentType.ToString()].Equals(quoteAccept.InstrumentType)))
                        tradeAdaptor.Get(Params.ParseTradeAction.ToString()).Set(quoteAccept.Quote);//to trade engine
                    
                    instrument.QuoteAccept(quoteAccept);
                }
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuotePublish(IFunction func)
        {
            try
            {
                var quotePublish = new FinPlusQuoteWrapper(func);
                IFunction quoteAdaptor;
                if (_quoteAdaptors.TryGetValue(quotePublish.SourceName, out quoteAdaptor) && _publishQuotes.TryAdd(quotePublish.QuoteRef, quotePublish))
                    quoteAdaptor.Get(Params.PublishAction.ToString()).Set(quotePublish.Quote);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteCancelPublish(IFunction func)
        {
            try
            {
                var cancelPublish = new FinPlusQuoteWrapper(func);
                IFunction quoteAdaptor;
                if (_quoteAdaptors.TryGetValue(cancelPublish.SourceName, out quoteAdaptor))
                    quoteAdaptor.Get(Params.PublishAction.ToString()).Set(cancelPublish.Quote);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteExecutePublish(IFunction func)
        {
            try
            {
                var execute = new FinPlusQuoteWrapper(func);
                FinPlusQuoteWrapper quote; FinPlusQuoteInstrument instrument;
                if (_publishQuotes.TryGetValue(execute.QuoteRef, out quote) && _quoteInstruments.TryGetValue(quote.Instrument, out instrument))
                    instrument.QuoteExecutePublish(execute);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteDeclinePublish(IFunction func)
        {
            try
            {
                var quoteDeclinePublish = new FinPlusQuoteWrapper(func);
                FinPlusQuoteWrapper quote; IFunction quoteAdaptor;
                if (_publishQuotes.TryRemove(quoteDeclinePublish.QuoteRef, out quote) && _quoteAdaptors.TryGetValue(quoteDeclinePublish.SourceName, out quoteAdaptor))
                    quoteAdaptor.Get(Params.PublishAction.ToString()).Set(quoteDeclinePublish.Quote); //to ecn
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void QuoteAcceptPublish(IFunction func)
        {
            try
            {
                var quoteAcceptPublish = new FinPlusQuoteWrapper(func);
                FinPlusQuoteWrapper quote;
                IFunction quoteAdaptor;
                FinPlusQuoteInstrument instrument;
                if (_quoteInstruments.TryGetValue(quoteAcceptPublish.Instrument, out instrument) && _publishQuotes.TryRemove(quoteAcceptPublish.QuoteRef, out quote) && _quoteAdaptors.TryGetValue(quoteAcceptPublish.SourceName, out quoteAdaptor))
                {
                    foreach (var tradeAdaptor in _tradeAdaptorConn.Adaptor.Values.Where(f => f[Params.InstrumentType.ToString()].Equals(quoteAcceptPublish.InstrumentType)))
                        tradeAdaptor.Get(Params.ParseTradeAction.ToString()).Set(quoteAcceptPublish.Quote); //to trade engine

                    quoteAdaptor.Get(Params.PublishAction.ToString()).Set(quoteAcceptPublish.Quote); //to ecn
                }
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void BookedTrade(IFunction func)
        {
            try
            {
                var trade = Function.FromString(func[Params.Tag.ToString()].ToString());
                var item = trade.Get(Params.Instrument.ToString());
                trade.Remove(item);
                FinPlusQuoteInstrument instrument;
                if (_quoteInstruments.TryGetValue(item.Value, out instrument))
                    instrument.BookedTrade(trade);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        //cmds 
        private enum Cmds { SaveCloses, Quote, QuoteCancel, QuoteExecute, QuoteMissed, QuoteDecline, QuoteAccept, QuotePublish, QuoteCancelPublish, QuoteExecutePublish, QuoteAcceptPublish, QuoteDeclinePublish, BookedTrade }

        //events
        private void Quote_Receive(object s, PropertyChangedEventArgs a) { CmdRouter(s); }
        private void Quote_Publish(IFunction func) { CmdExecute(func); }
        private void Trade_Done(object s, PropertyChangedEventArgs a) { CmdExecute(new Function(Cmds.BookedTrade.ToString(), Id, User, -1, ((IItem)s).Value)); }
    }
}

